package cn.dansj.common.utils.spring.context.spel;

import cn.dansj.common.processor.meta.WriteToAutoConfiguration;
import cn.dansj.common.utils.reflect.ClassUtils;
import cn.dansj.common.utils.spring.context.condition.ConditionalOnClass;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * spel Key生成器
 * expression ${#method}.${#instance}.${#domain}   ${#instance.name}
 */
@WriteToAutoConfiguration
@ConditionalOnClass(ExpressionParser.class)
@Configuration
public class KeyBuilder implements BeanPostProcessor {
    private static final ExpressionParser PARSER = new SpelExpressionParser();

    private final BeanResolver beanResolver;

    private final ParserContext template = new TemplateParserContext("${", "}");

    public KeyBuilder(BeanFactory beanFactory) {
        this.beanResolver = new BeanFactoryResolver(beanFactory);
    }

    public String buildKey(MethodInvocation invocation, String[] definitionKeys) throws NoSuchMethodException {
        return buildKey(invocation.getMethod(), invocation.getArguments(), definitionKeys);
    }

    public String buildKey(ProceedingJoinPoint joinPoint, String[] definitionKeys) throws NoSuchMethodException {
        return buildKey(getMethod(joinPoint), joinPoint.getArgs(), definitionKeys);
    }

    public String buildKey(Method method, Object[] arguments, String[] definitionKeys) throws NoSuchMethodException {
        if (definitionKeys.length > 1 || !"".equals(definitionKeys[0])) {
            return getSpelDefinitionKey(definitionKeys, method, arguments);
        }
        return "";
    }

    private String getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) throws NoSuchMethodException {
        StandardEvaluationContext context = new MethodEvaluationContext(TypedValue.NULL, method, parameterValues);
        context.setBeanResolver(beanResolver);
        List<String> definitionKeyList = new ArrayList<>(definitionKeys.length);
        for (String definitionKey : definitionKeys) {
            if (definitionKey != null && !definitionKey.isEmpty()) {
                String key = PARSER.parseExpression(definitionKey, template).getValue(context, String.class);
                definitionKeyList.add(key);
            }
        }
        return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
    }

    public static Method getMethod(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();
        return ClassUtils.getMethod(targetClass, signature.getName(), signature.getMethod().getParameterTypes());
    }
}